﻿using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.GamerServices;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Media;
using Microsoft.Xna.Framework.Net;
using Microsoft.Xna.Framework.Storage;

namespace XnaUtility.Collision
{
    class SlotCompare : IEqualityComparer<Slot>
    {
        public static readonly SlotCompare Instance = new SlotCompare();

        #region IEqualityComparer<TestEnum> Members

        public bool Equals(Slot x, Slot y)
        {
            //return (x == y);
            if (x.X == y.X && x.Y == y.Y) return true;
            else return false;
        }

        public int GetHashCode(Slot obj)
        {
            return obj.Y * Spatial2DHash<Slot>.CellWidth + obj.X;
        }

        #endregion
    }

    public struct Slot : IComparable
    {
        public int X;
        public int Y;

        #region IComparable Members

        public int CompareTo(object obj)
        {

            Slot other = (Slot)obj;

            if (other.X == this.X && other.Y == this.Y) return 0;

            if (other.X < this.X && other.Y < this.Y) return -1;
            else if (other.X < this.X && other.Y > this.Y) return 1;
            else if (other.X == this.X && other.Y < this.Y) return -1;
            else if (other.X == this.X && other.Y > this.Y) return 1;
            else if (other.X > this.X && other.Y < this.Y) return -1;
            else if (other.X > this.X && other.Y > this.Y) return 1;

            return 0; // failsafe
        }

        #endregion

        public static bool operator ==(Slot lh, Slot rh)
        {
            if (rh.X == lh.X && rh.Y == lh.Y) return true;
            else return false;
        }

        public static bool operator !=(Slot lh, Slot rh)
        {
            return (lh.X != rh.X || lh.Y != rh.Y);
        }

        public bool IsEqual(ref Slot other)
        {
            if (other.X == this.X && other.Y == this.Y) return true;
            else return false;
        }
    }

    public class Spatial2DHash<T>
    {
        Dictionary<Slot, List<T>> hashTable;
        int cellSize;
        List<Slot> insertList;
        List<Slot> checkList;
        List<T> getList;

        public static int CellWidth = 0;

        public int CellSize
        {
            get { return cellSize; }
        }

        Point bucketMin;
        Point bucketMax;

        public Spatial2DHash(int cellSize)
        {
            hashTable = new Dictionary<Slot, List<T>>(SlotCompare.Instance);
            this.cellSize = cellSize;
            insertList = new List<Slot>();
            checkList = new List<Slot>();
            getList = new List<T>();
        }

        public void CreateBuckets(Point min, Point max)
        {
            Slot temp;
            bucketMax = max;
            bucketMin = min;
            Spatial2DHash<T>.CellWidth = max.X - min.X + 1;

            for (int row = min.Y; row <= max.Y; row++)
            {
                for (int col = min.X; col <= max.X; col++)
                {
                    temp.X = col;
                    temp.Y = row;
                    if (!hashTable.ContainsKey(temp))
                    {
                        hashTable.Add(temp, new List<T>());
                    }
                } //end col
            } //end row
        } //end CreateBuckets

        public Slot InsertObject(T obj, Vector2 location)
        {
            Slot temp;

            temp.X = (int)(location.X / cellSize);
            temp.Y = (int)(location.Y / cellSize);

            try
            {
                hashTable[temp].Add(obj);
            }
            catch (System.Collections.Generic.KeyNotFoundException e)
            {

                bucketMin.X = (int)MathHelper.Min(bucketMin.X, temp.X);
                bucketMin.Y = (int)MathHelper.Min(bucketMin.Y, temp.Y);

                bucketMax.X = (int)MathHelper.Max(bucketMax.X, temp.X);
                bucketMax.Y = (int)MathHelper.Max(bucketMax.Y, temp.Y);

                CreateBuckets(bucketMin, bucketMax);

                hashTable[temp].Add(obj);
            }


            return temp;
        }

        public void InsertObject(T obj, Rectangle box, ref Slot[] slots)
        {
            //insertList.Clear();
            int index = 0;
            Slot temp;

            Slot minSlot;
            Slot maxSlot;

            minSlot.X = (int)(box.X / cellSize);
            minSlot.Y = (int)(box.Y / cellSize);

            maxSlot.X = (int)((box.Width + box.X) / cellSize);
            maxSlot.Y = (int)((box.Height + box.Y) / cellSize);

            for (int row = minSlot.Y; row <= maxSlot.Y; row++)
            {
                for (int col = minSlot.X; col <= maxSlot.X; col++)
                {
                    //temp = new Slot();
                    temp.X = col;
                    temp.Y = row;
                    //handle a non-existant key
                    try
                    {
                        hashTable[temp].Add(obj);
                    }
                    catch (System.Collections.Generic.KeyNotFoundException e)
                    {

                        bucketMin.X = (int)MathHelper.Min(bucketMin.X, temp.X);
                        bucketMin.Y = (int)MathHelper.Min(bucketMin.Y, temp.Y);

                        bucketMax.X = (int)MathHelper.Max(bucketMax.X, temp.X);
                        bucketMax.Y = (int)MathHelper.Max(bucketMax.Y, temp.Y);

                        CreateBuckets(bucketMin, bucketMax);

                        hashTable[temp].Add(obj);
                    }
                    slots[index].X = temp.X;
                    slots[index].Y = temp.Y;
                    index++;
                }
                if (index < slots.Length)
                {
                    slots[index].X = Int32.MinValue;
                    slots[index].Y = Int32.MinValue;
                }
            }
        }

        public Slot[] InsertObject(T obj, Rectangle box)
        {
            insertList.Clear();
            Slot temp;

            Slot minSlot;
            Slot maxSlot;

            minSlot.X = (int)(box.X / cellSize);
            minSlot.Y = (int)(box.Y / cellSize);

            maxSlot.X = (int)((box.Width + box.X) / cellSize);
            maxSlot.Y = (int)((box.Height + box.Y) / cellSize);

            for (int row = minSlot.Y; row <= maxSlot.Y; row++)
            {
                for (int col = minSlot.X; col <= maxSlot.X; col++)
                {
                    //temp = new Slot();
                    temp.X = col;
                    temp.Y = row;
                    //handle a non-existant key
                    try
                    {
                        hashTable[temp].Add(obj);
                    }
                    catch (System.Collections.Generic.KeyNotFoundException e)
                    {

                        bucketMin.X = (int)MathHelper.Min(bucketMin.X, temp.X);
                        bucketMin.Y = (int)MathHelper.Min(bucketMin.Y, temp.Y);

                        bucketMax.X = (int)MathHelper.Max(bucketMax.X, temp.X);
                        bucketMax.Y = (int)MathHelper.Max(bucketMax.Y, temp.Y);

                        CreateBuckets(bucketMin, bucketMax);

                        hashTable[temp].Add(obj);
                    }
                    insertList.Add(temp);
                }
            }

            return insertList.ToArray();
        }

        /// <summary>
        /// Gets all objects in the bucket.  Can return and empty list
        /// </summary>
        /// <param name="location"></param>
        /// <returns></returns>
        public IList<T> GetObjectsAt(Vector2 location)
        {
            Slot slot;
            slot.X = (int)(location.X / cellSize);
            slot.Y = (int)(location.Y / cellSize);

            return hashTable[slot].AsReadOnly();
        }

        /// <summary>
        /// Returns all objects in all buckets touched by the rectangle.  Can return an empty lits
        /// </summary>
        /// <param name="location"></param>
        /// <returns></returns>
        public T[] GetObjectsAt(Rectangle location)
        {
            getList.Clear();

            Slot minSlot;
            Slot maxSlot;
            Slot slot;

            minSlot.X = (int)(location.X / cellSize);
            minSlot.Y = (int)(location.Y / cellSize);

            maxSlot.X = (int)((location.Width + location.X) / cellSize);
            maxSlot.Y = (int)((location.Height + location.Y) / cellSize);

            for (int row = minSlot.Y; row <= maxSlot.Y; row++)
            {
                for (int col = minSlot.X; col <= maxSlot.X; col++)
                {
                    slot.X = col;
                    slot.Y = row;
                    //for (int i = 0; i < hashTable[slot].Count; i++) result.Add(hashTable[slot][i]);
                    getList.AddRange(hashTable[slot]);
                }
            }
            return getList.ToArray();
        }

        public IList<T> GetObjectsAt(Slot slot)
        {
            //return hashTable[slot].AsReadOnly();
            return hashTable[slot];
        }

        public bool RemoveObjectAt(T obj, Vector2 location)
        {
            Slot slot;
            slot.X = (int)(location.X / cellSize);
            slot.Y = (int)(location.Y / cellSize);
            return RemoveObjectAt(obj, slot);
        }

        public bool RemoveObjectAt(T obj, Slot slot)
        {
            return hashTable[slot].Remove(obj);
        }


        public Slot CheckSlot(Vector2 location)
        {
            Slot result;
            result.X = (int)(location.X / cellSize);
            result.Y = (int)(location.Y / cellSize);
            return result;
        }

        public Slot[] CheckSlots(Rectangle location)
        {
            checkList.Clear();
            Slot cur, min, max;
            min.X = (int)(location.X / cellSize);
            min.Y = (int)(location.Y / cellSize);
            max.X = (int)((location.X + location.Width) / cellSize);
            max.Y = (int)((location.Y + location.Height) / cellSize);

            for (int row = min.Y; row <= max.Y; row++)
            {
                for (int col = min.X; col <= max.X; col++)
                {
                    cur.X = col;
                    cur.Y = row;
                    checkList.Add(cur);
                }
            }

            return checkList.ToArray(); ;

        }

        /// <summary>
        /// Compares to see if the slots are correct
        /// </summary>
        /// <param name="slots">An array of slots that will be check against the location</param>
        /// <param name="location">The location to calculate slots for</param>
        /// <returns>Returns true if the slots match, false if they don't</returns>
        public bool CheckSlots(ref Slot[] slots, Rectangle location)
        {
            bool result = true;

            Slot min, max;
            min.X = (int)(location.X / cellSize);
            min.Y = (int)(location.Y / cellSize);
            max.X = (int)((location.X + location.Width) / cellSize);
            max.Y = (int)((location.Y + location.Height) / cellSize);

            for (int i = 0; i < slots.Length; i++)
            {
                if (
                    slots[i].X < min.X ||
                    slots[i].X > max.X ||
                    slots[i].Y < min.Y ||
                    slots[i].Y > max.Y
                    )
                {
                    result = false;
                    break;
                }

            }

            return result;
        }


    }
}
